package com.atguigu.gulimall.seckill.service.impl;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.mq.SeckillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberResVo;
import com.atguigu.gulimall.seckill.feign.CouponFeignService;
import com.atguigu.gulimall.seckill.feign.ProductFeignService;
import com.atguigu.gulimall.seckill.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.seckill.service.SeckillService;
import com.atguigu.gulimall.seckill.to.SeckillSkuRedisTo;
import com.atguigu.gulimall.seckill.vo.SeckillSessionWithSkus;
import com.atguigu.gulimall.seckill.vo.SeckillSkuVo;
import com.atguigu.gulimall.seckill.vo.SkuInfoVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    ProductFeignService productFeignService;

    private final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";

    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Override
    public void uploadSeckillSkuLatest3Days() {
        R r = couponFeignService.getLates3DaysSession();
        if (r.getCode() == 0) {
            //上架商品
            Object session = r.get("session");
            String s = JSON.toJSONString(session);
            List<SeckillSessionWithSkus> sessionData = JSON.parseObject(s, new TypeReference<List<SeckillSessionWithSkus>>() {
            });


            //缓存到redis
            //1. 缓存活动信息
            saveSessionInfos(sessionData);
            //2. 缓存活动的关联商品信息
            SaveSessionSkuInfos(sessionData);


        }

    }


    @SentinelResource(value = "getcurrentSeckillSkus111",blockHandler = "blockHandle1")
    @Override
    public List<SeckillSkuRedisTo> getcurrentSeckillSkus() {

        long time = new Date().getTime();
        try (Entry entry = SphU.entry("seckillSkus")){
            Set<String> keys = stringRedisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
            if(keys!=null&&keys.size()>0){
                for (String key : keys) {
                    String replace = key.replace(SESSIONS_CACHE_PREFIX, "");
                    String[] s = replace.split("_");
                    Long start = Long.parseLong(s[0]);
                    Long end = Long.parseLong(s[1]);

                    //当前时间在这个秒杀场次
                    if(start<=time&&time<=end){
                        List<String> range = stringRedisTemplate.opsForList().range(key, -100, 100);
                        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

                        List<String> strings = ops.multiGet(range);

                        if(strings!=null&&strings.size()>0){
                            List<SeckillSkuRedisTo> collect = strings.stream().map(item -> {
                                SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(item, SeckillSkuRedisTo.class);
                                return seckillSkuRedisTo;
                            }).collect(Collectors.toList());

                            return collect;
                        }
                        break;
                    }
                }
            }

        } catch (BlockException e) {
            System.out.println("getcurrentSeckillSkus 资源被限制了。。。。");
        }
        return null;
    }
    public List<SeckillSkuRedisTo> blockHandle1(BlockException  e){
        System.out.println("getcurrentSeckillSkus 原方法被降级了");
        return null;
    }
    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {

        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

        Set<String> keys = ops.keys();
        if(keys!=null&&keys.size()>0){
            String reg = "\\d_"+skuId;
            for (String key : keys) {
               if(Pattern.matches(reg,key)){
                   String s = ops.get(key);
                   SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(s, SeckillSkuRedisTo.class);


                   //随机码
                   long current = new Date().getTime();
                   if(current>=seckillSkuRedisTo.getStartTime()&&current<=seckillSkuRedisTo.getEndTime()){

                   }else{
                       seckillSkuRedisTo.setRandomCode(null);
                   }
                   return seckillSkuRedisTo;
               }

            }
        }

        return null;
    }

    @Override
    public String kill(String killId, String code, Integer num) {

        MemberResVo memberResVo = LoginUserInterceptor.loginUser.get();

        BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String o =  ops.get(killId);
        if(StringUtils.isEmpty(o)){
            return null;
        }


        SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(o, SeckillSkuRedisTo.class);
        //1. 检验时间合法性
        Long startTime = seckillSkuRedisTo.getStartTime();
        Long endTime = seckillSkuRedisTo.getEndTime();
        Long time = new Date().getTime();
        Long ttl = endTime-time;
        if(time>=startTime && time<=endTime){
            //2. 校验随机码和商品id
            String randomCode = seckillSkuRedisTo.getRandomCode();
           String skuId= seckillSkuRedisTo.getPromotionSessionId()+"_"+seckillSkuRedisTo.getSkuId();
           
           if(randomCode.equals(code) && killId.equals(skuId)){
               //3. 购物数量合理
               Integer seckillLimit = seckillSkuRedisTo.getSeckillLimit().intValue();
             if(num<=seckillLimit){
                 //4. 验证这个人是否已经购买过 幂等性 如果购买成功，就去站位  user_id_sessionId_skuId
                 String redisKey=memberResVo.getId()+"_"+skuId;
                 //自动过期
                 Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, String.valueOf(num),ttl, TimeUnit.MILLISECONDS);
                 if(b){
                     //成功说明没有买过
                     RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + code);

                     try {
                         boolean b1 = semaphore.tryAcquire(num);
                       //秒杀成功
                         // 快速下单 发送mq消息

                         if(b1){
                             String timeId = IdWorker.getTimeId();
                             SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                             seckillOrderTo.setMemberId(memberResVo.getId());
                             seckillOrderTo.setNum(num);
                             seckillOrderTo.setSeckillPrice(seckillSkuRedisTo.getSeckillPrice());
                             seckillOrderTo.setOrderSn(timeId);
                             seckillOrderTo.setSkuId(seckillSkuRedisTo.getSkuId());
                             seckillOrderTo.setPromotionSessionId(seckillSkuRedisTo.getPromotionSessionId());

                             rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",seckillOrderTo);

                             return timeId;
                         }
                         return null;
                     } catch (Exception e) {
                         return null;
                     }

                 }else{
                     return null;
                 }
             }
           }else{
               return null;
           }
        }else{
            return null;
        }
        return null;
    }

    private void saveSessionInfos(List<SeckillSessionWithSkus> sessionData) {

        for (SeckillSessionWithSkus session : sessionData) {
            long startTime = session.getStartTime().getTime();
            long endTime = session.getEndTime().getTime();

            String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
            List<SeckillSkuVo> relationEntities = session.getRelationEntities();
            List<String> list = relationEntities.stream().map(item -> item.getPromotionSessionId().toString()+"_"+item.getSkuId().toString()).collect(Collectors.toList());
            Boolean has = stringRedisTemplate.hasKey(key);
            if (!has) {
                //缓存活动信息
                stringRedisTemplate.opsForList().leftPushAll(key, list);
            }
        }
    }


    private void SaveSessionSkuInfos(List<SeckillSessionWithSkus> sessions) {


        for (SeckillSessionWithSkus session : sessions) {

            BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

            for (SeckillSkuVo seckillSkuVo : session.getRelationEntities()) {
                //4. 秒杀随机码
                String token = UUID.randomUUID().toString().replace("-", "");

                if (!ops.hasKey(seckillSkuVo.getPromotionSessionId().toString()+"_"+seckillSkuVo.getSkuId().toString())) {
                    SeckillSkuRedisTo seckillSkuRedisTo = new SeckillSkuRedisTo();

                    //1.sku的基本数据
                    R r = productFeignService.info(seckillSkuVo.getSkuId());
                    if (r.getCode() == 0) {
                        Object skuInfo = r.get("skuInfo");
                        String jsonString = JSON.toJSONString(skuInfo);
                        SkuInfoVo skuInfoVo = JSON.parseObject(jsonString, SkuInfoVo.class);

                        seckillSkuRedisTo.setSkuInfoVos(skuInfoVo);
                    }

                    //2. sku的秒杀信息
                    BeanUtils.copyProperties(seckillSkuVo, seckillSkuRedisTo);

                    //3. 设置上当前商品的秒杀时间
                    seckillSkuRedisTo.setStartTime(session.getStartTime().getTime());
                    seckillSkuRedisTo.setEndTime(session.getEndTime().getTime());

                    seckillSkuRedisTo.setRandomCode(token);
                    String s = JSON.toJSONString(seckillSkuRedisTo);

                    ops.put(seckillSkuRedisTo.getPromotionSessionId().toString()+"_"+seckillSkuVo.getSkuId().toString(), s);

                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                    //4. 商品秒杀的信号量
                    semaphore.trySetPermits(seckillSkuVo.getSeckillCount().intValue());

                }

            }

        }

    }
}
